﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Diagnostics;

namespace Xceed.Silverlight.Data
{
  internal class MoveAndFetchResult : DataEnumeratorResult
  {
    #region Constructor

    protected internal MoveAndFetchResult(
      Nullable<long> actualMove,
      ICollection fetchedObjects,
      bool approximateMove,
      long expectedMoveCount,
      int expectedFetchCount,
      bool result,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount )
    {
      this.Initialize( actualMove, fetchedObjects, approximateMove, expectedMoveCount, expectedFetchCount, result, endPositionOffset, totalCount );
    }

    #endregion

    #region ActualMoveCount Property

    internal Nullable<long> ActualMoveCount
    {
      get
      {
        return m_actualMove;
      }
    }

    private Nullable<long> m_actualMove;

    #endregion

    #region ApproximateMove Property

    internal bool ApproximateMove
    {
      get
      {
        return m_approximateMove;
      }
    }

    private bool m_approximateMove;

    #endregion

    #region ExpectedMoveCount Property

    internal long ExpectedMoveCount
    {
      get
      {
        return m_expectedMoveCount;
      }
    }

    private long m_expectedMoveCount;

    #endregion

    #region FetchedObjects Property

    internal ICollection FetchedObjects
    {
      get
      {
        return m_fetchedObjects;
      }
    }

    private ICollection m_fetchedObjects;

    #endregion

    #region ExpectedFetchCount Property

    internal int ExpectedFetchCount
    {
      get
      {
        return m_expectedFetchCount;
      }
    }

    private int m_expectedFetchCount;

    #endregion

    #region Result Property

    internal bool Result
    {
      get
      {
        return m_result;
      }
    }

    private bool m_result;

    #endregion

    #region EndPositionOffset Property

    internal RelativeOffset EndPositionOffset
    {
      get
      {
        return m_endPositionOffset;
      }
    }

    private RelativeOffset m_endPositionOffset;

    #endregion

    #region TotalCount Property

    internal EstimateInt64 TotalCount
    {
      get
      {
        return m_totalCount;
      }
    }

    private EstimateInt64 m_totalCount;

    #endregion

    protected void Initialize(
      Nullable<long> actualMove,
      ICollection fetchedObjects,
      bool approximateMove,
      long expectedMoveCount,
      int expectedFetchCount,
      bool result,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount )
    {
      if( MoveAndFetchResult.IsInvalidState(
            expectedMoveCount,
            expectedFetchCount,
            actualMove,
            fetchedObjects,
            result,
            endPositionOffset,
            totalCount ) )
        throw new DataInternalException();

      if( endPositionOffset != null && totalCount != null )
      {

        long maximumReferencePosition = ( result ) ? totalCount.Value : totalCount.Value + 1;

        if( endPositionOffset.Value > maximumReferencePosition )
        {
          Debug.Assert( endPositionOffset.IsEstimate || totalCount.IsEstimate,
            "This invalid case is supposed to be handled by IsInvalidState call" );

          if( totalCount.IsEstimate )
          {
            totalCount = new EstimateInt64( endPositionOffset.Value, true );
          }
          else
          {
            endPositionOffset = new RelativeOffset( new EstimateInt64( maximumReferencePosition, true ), endPositionOffset.ReferencePosition );
          }
        }
      }

      m_actualMove = actualMove;
      m_approximateMove = approximateMove;
      m_expectedMoveCount = expectedMoveCount;
      m_fetchedObjects = fetchedObjects;
      m_expectedFetchCount = expectedFetchCount;
      m_result = result;

      m_endPositionOffset = endPositionOffset;
      m_totalCount = totalCount;
    }

    private static bool IsInvalidState(
      long expectedMoveCount, 
      int expectedFetchCount,
      Nullable<long> actualMoveCount, 
      ICollection fetchedObjects, 
      bool result,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount )
    {
      bool isInvalid = ( !actualMoveCount.HasValue && result )
                    || ( actualMoveCount.HasValue && ( expectedMoveCount != actualMoveCount ) && result )
                    || ( actualMoveCount.HasValue && ( expectedMoveCount != actualMoveCount ) && ( fetchedObjects != null ) )
                    || ( ( fetchedObjects == null ) && result )
                    || ( ( fetchedObjects != null ) && ( ( fetchedObjects.Count * DataUtils.GetUnit( expectedFetchCount ) ) == expectedFetchCount ) && !result )
                    || ( ( fetchedObjects != null ) && ( ( fetchedObjects.Count * DataUtils.GetUnit( expectedFetchCount ) ) != expectedFetchCount ) && result );

      if( !isInvalid )
      {
        isInvalid = (endPositionOffset != null && !endPositionOffset.IsEstimate && endPositionOffset.Value < 0)
                 || ( totalCount != null && !totalCount.IsEstimate && totalCount.Value < 0 );
      }

      if( !isInvalid )
      {
        if( totalCount != null )
        {
          Nullable<int> actualFetchCount = ( fetchedObjects != null ) ? fetchedObjects.Count : ( Nullable<int> )null;
          Nullable<long> span = MoveAndFetchResult.CalculateDisplacementSpan( expectedMoveCount, expectedFetchCount, actualMoveCount, actualFetchCount );

          if( span != null )
          {
            isInvalid = ( !totalCount.IsEstimate && totalCount.Value < span.Value - 1 );
          }
        }
      }

      if( !isInvalid
        && endPositionOffset != null 
        && totalCount != null )
      {
        if( !endPositionOffset.IsEstimate && !totalCount.IsEstimate )
        {
          isInvalid = ( endPositionOffset.Value > totalCount.Value + 1 );
        }
      }

      Debug.Assert( !isInvalid );

      return isInvalid;
    }

    private static Nullable<long> CalculateDisplacementSpan( long expectedMoveCount, int expectedFetchCount, Nullable<long> actualMoveCount, Nullable<int> actualFetchCount )
    {
      Nullable<long> span = null;

      if( actualFetchCount != null )
      {
        span = Math.Abs( expectedMoveCount );
        int itemCount = actualFetchCount.Value;
        if( itemCount > 0 )
        {
          if( DataUtils.GetUnit( expectedMoveCount ) == DataUtils.GetUnit( expectedFetchCount ) )
          {
            span += itemCount - 1;
          }
          else
          {
            span = Math.Max( span.Value, itemCount );
          }
        }
      }
      else if( actualMoveCount != null )
      {
        span = Math.Abs( actualMoveCount.Value );
      }

      return span;
    }

#if DEBUG
    public override string ToString()
    {
      string debugFetchData =
        ( this.FetchedObjects == null )
        ? "(null)"
        : string.Format( "(Count={0})", this.FetchedObjects.Count );

      string debugOffset =
        ( this.EndPositionOffset == null )
        ? "(null)"
        : this.EndPositionOffset.ToString();

      string debugTotal =
        ( this.TotalCount == null )
        ? "(null)"
        : string.Format( "(Value={0}, IsEstimate={1})", this.TotalCount.Value, this.TotalCount.IsEstimate );

      string debugSourcePosition =
        ( this is JumpMoveAndFetchResult )
        ? ( ( JumpMoveAndFetchResult )this ).SourcePosition.ToString()
        : "CURRENT";

      return string.Format(
        "Source={0}, ExMove={1}, ExFetch={2}, ActMove={3}, FetchCount={4}, Result={5}, " +
        "EndOffset={6}, Total={7}",
        debugSourcePosition, this.ExpectedMoveCount, this.ExpectedFetchCount, this.ActualMoveCount,
        debugFetchData, this.Result, debugOffset, debugTotal );
    }
#endif //DEBUG
  }
}
